
;boot把loader加载进内存并不是和BIOS把boot加载进内存的方式一样，可以把BIOS理解为加载器
;所以bios加载boot的时候，boot编译后的地址能正常映射。。
;但是我们自己写的boot加载loader的时候，loader被编译的地址可不是一样的...
;================================================================
 ;   mov si, msg    ;将msg地址赋给si

;putLoop:
;    mov al, [si]    ;等同 mov al, byte [si]  将si地址开始的一个字节的值放入al中
 ;   add si, 1    ;si = si+1
 ;   cmp al, 0    ;al与0比较
 ;   je pause        ;如上面比较相等 则跳转到fin 不相等则不跳转,执行后面命令
 ;   mov ah, 0x0e
 ;   int 0x10
 ;   jmp putLoop
  ;pause:
 ;   jmp $
;==============================================================================
;假设loader是上面的代码，很明显编译后，msg的地址是假设是0x2000
;如果我们 mov al,[si] 问题就来了.... 这个si地址是0x2000.....
;而不是我们的段地址+2000 所以取出来的数据有问题 所以改成mov al,[cs:si] 即可

%include "const.inc"



org 0x90000    ;主引导记录的内存地址
[bits 16]
align 16

entry:

  ;先回个车 emmmm 调用显存之后回车好像无效。。
 ; mov al, 0x0a
 ; int 0x10

  ;设置光标的位置 因为要显示文字，
  ;默认之后的输出文字 在21行 2列显示...
  mov ah,02H
  mov dh,21;行
  mov dl,0 ;列
  int 10h



  mov ax, cs
  mov ss, ax
  mov ds, ax
  mov es, ax

  

clear_sceen:
    ;清屏 由于调用显存后，利用终端设置光标好像无效
    ;取消回车，直接清屏吧~
    mov ax, 0x2
    int 0x10



print:

    ;构造显存调用参数
    mov ax, 0xb800
    mov es, ax
    mov di, 0
    mov si,msg

.printf:

    mov al, [cs:si]
    cmp al, 0    ;al与0比较
    je setProtectMode
    mov byte [es: di], al
    inc si
    inc di
    mov byte [es: di], 0xcf ;背景闪烁设置
    inc di
    jmp .printf
   
  






;readHd:

    ;dx存入硬盘寄存器
   ; mov dx,0x1f2
   ; mov al,0x01    ;1 个扇区
    ;向0x1f2端口传数据
    ;out dx,al


setProtectMode:

    ;关中断
    cli

   

    
    ;开20 提高寻址空间
    in		al,0x92
    or		al,2
    out		0x92,al


  
    ;load GDTR
    lgdt	[gdt]



    ;set CR0 bit PE
    ;这里寄存器的名字就要改了，地址长度也有变化了。
    mov		eax,  cr0
    or		eax,  1
    mov		cr0,  eax
    jmp dword 0x08:p_mode_start


[bits 32]
align 32
p_mode_start:

  call set_page

 
 ;要将描述符表地址及偏移量写入内存 gdt_ptr，一会儿用新地址重新加载
 sgdt [gdt_ptr] ; 存储到原来 gdt 所有的位置
 
 ;将 gdt 描述符中视频段描述符中的段基址+0xc0000000 
 mov ebx, [gdt_ptr + 2] 
 or dword [ebx + 0x18 + 4], 0xc0000000 
 ;视频段是第 3 个段描述符，每个描述符是 8 字节，故 0x18 
 ;段描述符的高 4 字节的最高位是段基址的第 31～24 位
 
 ;将 gdt 的基址加上 0xc0000000 使其成为内核所在的高地址
 add dword [gdt_ptr + 2], 0xc0000000 
 
 add esp, 0xc0000000 ; 将栈指针同样映射到内核地址
 
 ; 把页目录地址赋给 cr3 
 mov eax, PAGE_DIR_TABLE_POS 
 mov cr3, eax 
 
 ; 打开 cr0 的 pg 位（第 31 位）
 mov eax, cr0 
 or eax, 0x80000000 
 mov cr0, eax 
 
 ;在开启分页后，用 gdt 新的地址重新加载
 lgdt [gdt_ptr] ; 重新加载
 
 mov byte [gs:160], 'V' 
;视频段段基址已经被更新，用字符 v 表示 virtual addr




    jmp  $

    

;pause:
 ;   call setProtectMode
 ;   jmp $



  ;|15~3| 段描述符索引，相当于就是数组的下标,因为段描述符是8字节长度
  ;|2|    TI 0表示描述符在GDT中，1表示描述符在LDT中。
  ;|1|    RPL特权级~00,11，2,3,了解一下
  ;dd 0x0000000  表示什么？从上面的注释来看
  ;这段数据是数据段描述符的下标为0，TI=0，RPL=0


gdtHead:
  ;dd 双字 4个字节=32位 两组就是64位 因为GDT的第一个描述符不能用，所以第一个描述表全设为0
  dd 0x0000000 
  dd 0x0000000 
  ;都是按字节读的数据，就算设定是以位的方式，依然还是可以处理
  ;==================================代码段======================
  ;dw 0x000ffff    ;段限制：0-15位
  ;dw 0x0000000    ;段基地址：0-15位
  ;====低32位
  ;段界限 0~15
   ;段描述符的第6字节属性（代码段可读写）
  ;FFFFF
  ;段基地址 16~31
  
  dd		0x0000ffff
	dd		0x00cf9A00

  dd		0x0000ffff
	dd		0x00cf9200

  ;====高32位  
 	
  ;db 0x0000000    ;段基地址：16-23(0~8)位
   ;<从右往左4位=1010>设置该段的type （代码段可执行、读）具体看参数 
   ;<从右往左5位> S 当该位是“ 0”时，表示是一个系统段；为“ 1”时，表示是一个代码段或者数据段（堆栈段也是特殊的数据段）。
   ;现在S为1，说明是一个非系统段（不是说不能给系统用，只是说系统有自己特别类型的段(DPL)，已经存在的!或许S我们可以作为判断是否堆栈段使用）。
   ;从右往左<6,7>位 DPL
   ;DPL 表示描述符的特权级（ Descriptor Privilege Level， DPL）。这两位用于指定段的特权级。
   ;共有 4 种处理器支持的特权级别，分别是 0、 1、 2、 3，其中 0 是最高特权级别， 3 是最低特权级别。
   ;刚进入保护模式时执行的代码具有令最高特权级 0（可以看成是从处理器那里继承来的），这些代码通常都是操作系统代码，
   ;因此它的特权级别最高。每当操作系统加载一个用户程序时，它通常都会指定一个稍低的特权级，比如 3 特权级。
   ;不同特权级别的程序是互相隔离的，其互访是严格限制的，而且有些处理器指令（特权指）只能由 0 特权级的程序来执行，为的就是安全
   ;<从右往左第8位=P=1>  P 位用于指示描述符所对应的段是否存在
   
  ;db 10001010b   
  ;db 11001111b    ;段描述符的第7字节属性：16-19位
  ;db 0x0000000    ;段描述符的最后一个字节是段基地址的第二部分：24-31位


  ;==================================数据段======================
 
 ; dw 0x000ffff    ;段限制：0-15位
  ;dw 0x0000000    ;段基地址：0-15位
  ;db 0x0000000    ;段基地址：16-23位
  ;db 10010010b    ;段描述符的第6字节属性（数据段可读写）
  ;db 11001111b    ;段描述符的第7字节属性：limit（位16-19）
 ; db 0x0000000    ;段描述符的最后一个字节是段基地址的第二部分：24-31位




set_page:  
  ;先把页目录占用的空间逐字节清 0 
  mov ecx, 4096 
  mov esi, 0 
.clear_page_dir: 
  mov byte [PAGE_DIR_TABLE_POS + esi], 0 
  inc esi 
  loop .clear_page_dir


;开始创建页目录项(PDE) 
.create_pde: ; 创建 Page Directory Entry 
  mov eax, PAGE_DIR_TABLE_POS 
  add eax, 0x1000 ; 此时 eax 为第一个页表的位置及属性
  mov ebx, eax ; 此处为 ebx 赋值，是为.create_pte 做准备，ebx 为基址
  
  ; 下面将页目录项 0 和 0xc00 都存为第一个页表的地址，每个页表表示 4MB 内存
  ; 这样 0xc03fffff 以下的地址和 0x003fffff 以下的地址都指向相同的页表
  ; 这是为将地址映射为内核地址做准备
  or eax, PG_US_U | PG_RW_W | PG_P 
  ; 页目录项的属性 RW 和 P 位为 1，US 为 1，表示用户属性，所有特权级别都可以访问
  mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第 1 个目录项
  ;在页目录表中的第 1 个目录项写入第一个页表的位置(0x101000)及属性(7) 
  mov [PAGE_DIR_TABLE_POS + 0xc00], eax 
  ; 一个页表项占用 4 字节
  ; 0xc00 表示第 768 个页表占用的目录项，0xc00 以上的目录项用于内核空间
  ;也就是页表的 0xc0000000～0xffffffff 共计 1G 属于内核
  ; 0x0～0xbfffffff 共计 3G 属于用户进程
  sub eax, 0x1000 
  mov [PAGE_DIR_TABLE_POS + 4092], eax 
  ; 使最后一个目录项指向页目录表自己的地址

  ;下面创建页表项(PTE) 
  mov ecx, 256 ; 1M 低端内存 / 每页大小 4k = 256 
  mov esi, 0 
  mov edx, PG_US_U | PG_RW_W | PG_P ; 属性为 7，US=1，RW=1，P=1 
.create_pte: ; 创建 Page Table Entry 
  mov [ebx+esi*4],edx 
  ; 此时的 ebx 已经在上面通过 eax 赋值为 0x101000，也就是第一个页表的地址
  add edx,4096 
  inc esi 
  loop .create_pte 
  
  ;创建内核其他页表的 PDE 
  mov eax, PAGE_DIR_TABLE_POS 
  add eax, 0x2000 ; 此时 eax 为第二个页表的位置
  or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性 US､ RW 和 P 位都为 1 
  mov ebx, PAGE_DIR_TABLE_POS 
  mov ecx, 254 ; 范围为第 769～1022 的所有目录项数量
  mov esi, 769 
.create_kernel_pde: 
  mov [ebx+esi*4], eax 
  inc esi 
  add eax, 0x1000 
  loop .create_kernel_pde
.F:
    ret	

gdt:
  dw gdt - gdtHead - 1
  dd gdtHead

msg:
  ;回车
 ; DB 0x0a, 0x0a
  DB "loadering~  of booksostest...."
str_run_p_mode:
  ;回车
 ; DB 0x0a, 0x0a
  DB "P"
  times (4096-($-$$)) db 0
